package org.jflame.commons.config;

import org.jflame.commons.convert.Converter;
import org.jflame.commons.convert.ConverterRegistry;
import org.jflame.commons.convert.ValueReaderSupport;
import org.jflame.commons.reflect.BeanHelper;
import org.jflame.commons.util.ArrayHelper;
import org.jflame.commons.util.NumberHelper;
import org.jflame.commons.util.StringHelper;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.Objects;

import org.apache.commons.lang3.exception.ExceptionUtils;

/**
 * 配置项值读取接口
 * 
 * @author charles.zhang
 */
public abstract class ConfigReader implements ValueReaderSupport {

    /**
     * 配置项统一前缀
     */
    public final static String JFLAME_CFG_KEY_PREFIX = "jflame";

    /**
     * 判断是否有这个配置项
     * 
     * @param configKey
     * @return
     */
    public abstract boolean hasKey(String configKey);

    @Override
    public <T> T getValue(final String configKey, T defaultValue, final Converter<String,T> valueConverter) {
        Objects.requireNonNull(configKey, "configKey must not be null");
        String value = getValue(configKey);
        T result = null;
        if (StringHelper.isNotEmpty(value)) {
            value = value.trim();
            EncryptedValueResolver valueResolver = getEncryptedValueResolver();
            if (valueResolver != null) {
                value = valueResolver.resolve(value);
            }
            result = valueConverter.convert(value);
        }
        return Objects.isNull(result) ? defaultValue : result;
    }

    /**
     * 获取加密值的解密类,默认null
     * 
     * @return
     */
    public EncryptedValueResolver getEncryptedValueResolver() {
        return null;
    }

    /**
     * 实例化给定类型的bean,并将配置值自动注入到类型属性( 名称转换规则: KebabCase -> camelCase)
     * 
     * @param <T>
     * @param beanClass 对象类型Class
     * @param prefix 配置项名称统一前缀,可为null
     * @return
     */
    public <T> T bindBean(Class<T> beanClass, String prefix) {
        T bean = null;
        try {
            bean = beanClass.getDeclaredConstructor()
                    .newInstance();
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            return ExceptionUtils.rethrow(e);
        }
        if (bean != null) {
            boolean hasSet = bindBean(bean, prefix);
            if (!hasSet) {
                bean = null;
            }
        }
        return bean;
    }

    /**
     * 配置值自动注入到类型属性( 名称转换规则: KebabCase -> camelCase)
     * 
     * @param bean 对象
     * @param prefix 配置项名称统一前缀,可为null
     * @return 没有设置任何值返回false
     */
    @SuppressWarnings({ "unchecked" })
    public boolean bindBean(Object bean, String prefix) {
        PropertyDescriptor[] beanPropDescs = BeanHelper.getPropertyDescriptors(bean.getClass());
        char split = '.';
        if (prefix == null) {
            prefix = "";
        } else {
            if (StringHelper.endChar(prefix) != split) {
                prefix = prefix + split;
            }
        }

        String propName;
        Object newValue;
        Class<?> fieldClazz;
        Method writeMethod;
        boolean hasValue = false;
        for (PropertyDescriptor propDesc : beanPropDescs) {
            writeMethod = propDesc.getWriteMethod();
            if (writeMethod == null) {
                continue;
            }
            newValue = null;
            // java属性转为properties配置项名
            propName = prefix + StringHelper.camelToKebabCase(propDesc.getName());
            fieldClazz = propDesc.getPropertyType();
            try {
                if (BeanHelper.isSimpleValueType(fieldClazz)) {
                    if (!hasKey(propName)) {
                        propName = prefix + StringHelper.camelToUnderline(propDesc.getName());
                        if (!hasKey(propName)) {
                            continue;
                        }
                    }
                    if (CharSequence.class.isAssignableFrom(fieldClazz)) {
                        newValue = getValue(propName);
                        if ("".equals(newValue)) {
                            newValue = null;
                        }
                    } else if (NumberHelper.isNumberType(fieldClazz)) {
                        newValue = NumberHelper.parseNumber(getValue(propName), (Class<Number>) fieldClazz);
                    } else if (fieldClazz.isEnum()) {
                        newValue = getValue(propName);
                        if (newValue != null && "".equals(newValue)) {
                            newValue = Enum.valueOf(fieldClazz.asSubclass(Enum.class), getValue(propName));
                        }
                    } else if (Date.class.isAssignableFrom(fieldClazz)) {
                        newValue = getValue(propName, ConverterRegistry.getDefaultConverter(String.class, fieldClazz)
                                .get());
                    } else if (Temporal.class.isAssignableFrom(fieldClazz)) {
                        newValue = getValue(propName, ConverterRegistry.getDefaultConverter(String.class, fieldClazz)
                                .get());
                    } else if (fieldClazz == Boolean.class || fieldClazz == boolean.class) {
                        newValue = getBoolean(propName);
                    } else if (URI.class == fieldClazz) {
                        newValue = getValue(propName, v -> URI.create(v));
                    }
                    if (newValue != null) {
                        writeMethod.invoke(bean, newValue);
                        hasValue = true;
                    }
                    continue;
                } else {
                    // 简单数组,值作为逗号分隔形式
                    if (fieldClazz.isArray()) {
                        if (!hasKey(propName)) {
                            propName = prefix + StringHelper.camelToUnderline(propDesc.getName());
                            if (!hasKey(propName)) {
                                continue;
                            }
                            continue;
                        }
                        if (CharSequence.class.isAssignableFrom(fieldClazz.getComponentType())) {
                            newValue = getStringArray(propName);
                            if (newValue != null) {
                                writeMethod.invoke(bean, newValue);
                                hasValue = true;
                            }
                        } else if (Number.class.isAssignableFrom(fieldClazz.getComponentType())) {
                            String[] tmpStrArr = getStringArray(propName);
                            if (tmpStrArr != null) {
                                newValue = ArrayHelper.toNumberArray(tmpStrArr,
                                        (Class<Number>) fieldClazz.getComponentType());
                                writeMethod.invoke(bean, newValue);
                                hasValue = true;
                            }
                        }
                        // 其他类型忽略
                        continue;
                    } else if (!fieldClazz.getName()
                            .startsWith("java.")
                            && !fieldClazz.getName()
                                    .startsWith("javax.")) {
                        // 作为一个实体类处理
                        newValue = bindBean(fieldClazz, propName + split);
                        if (newValue != null) {
                            writeMethod.invoke(bean, newValue);
                            hasValue = true;
                        }
                        continue;
                    }
                }

            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                ExceptionUtils.rethrow(e);
            }
        }
        return hasValue;
    }

}
